#include <QVariant>
#include <QDir>
#include <QFileInfo>
#include <QHeaderView>
#include <QDebug>
#include <QAction>
#include <QMenu>
#include <QContextMenuEvent>
#include <QInputDialog>
#include <QMessageBox>
#include <QClipboard>
#include <QApplication>
#include <QMimeData>
#include <QUrl>
#include <QFileInfo>
#include <QKeyEvent>

#include "dirtree.h"
#include "../utils.h"

namespace Jcr
{

static const int DataColumn = 0;
static const int DataRole = Qt::UserRole + 27;
static const int ItemTypeRole = Qt::UserRole + 28;

class DirTreePrivate
{
public:
    DirTreePrivate(DirTree* q) : q_ptr(q) { init(); }

    void init();
    QAction* createAction(const QString& text, const QIcon& icon, const char* slot = nullptr);
    QAction* createAction(const QString& text, const char* slot = nullptr);
    bool brandExists(const QString& brand);
    bool modelExists(const QString& model);
    bool childExists(QTreeWidgetItem* parent, const QString& text);

    DirTree* q_ptr;
    QString root;
    QString currentDir;
    QTreeWidgetItem* rootItem = nullptr;

    QMenu* rootMenu;
    QMenu* brandMenu;
    QMenu* modelMenu;
};

void DirTreePrivate::init()
{
    QAction* locateAction = createAction(QObject::tr("Locate"), SLOT(locate()));
    QAction* copyTextAction = createAction(QObject::tr("Copy Text to Clipboard"), SLOT(copyTextToClipboard()));
    QAction* copyDataAction = createAction(QObject::tr("Copy Data to Clipboard"), SLOT(copyDataToClipboard()));

    rootMenu = new QMenu(q_ptr);
    rootMenu->addAction(createAction(QObject::tr("Add Brand"), SLOT(addBrand())));

    brandMenu = new QMenu(q_ptr);
    brandMenu->addAction(createAction(QObject::tr("Add Model"), SLOT(addModel())));
    brandMenu->addAction(createAction(QObject::tr("Rename Brand"), SLOT(renameBrand())));
    brandMenu->addAction(createAction(QObject::tr("Delete Brand"), SLOT(deleteBrand())));
    brandMenu->addSeparator();
    brandMenu->addAction(copyTextAction);
    brandMenu->addAction(copyDataAction);
    brandMenu->addSeparator();
    brandMenu->addAction(locateAction);

    modelMenu = new QMenu(q_ptr);
    modelMenu->addAction(createAction(QObject::tr("Rename Model"), SLOT(renameModel())));
    modelMenu->addAction(createAction(QObject::tr("Delete Model"), SLOT(deleteModel())));
    modelMenu->addSeparator();
    modelMenu->addAction(copyTextAction);
    modelMenu->addAction(copyDataAction);
    modelMenu->addSeparator();
    modelMenu->addAction(locateAction);

    QClipboard* clipboard = QApplication::clipboard();
    q_ptr->connect(clipboard, SIGNAL(dataChanged()), q_ptr, SLOT(monitorClipboardDataChanged()));

}

QAction* DirTreePrivate::createAction(const QString& text, const QIcon& icon, const char* slot)
{
    QAction* act = new QAction(q_ptr);
    act->setText(text);
    act->setIcon(icon);
    q_ptr->connect(act, SIGNAL(triggered()), q_ptr, slot);
    return act;
}

QAction* DirTreePrivate::createAction(const QString& text, const char* slot)
{
    QAction* act = new QAction(q_ptr);
    act->setText(text);
    q_ptr->connect(act, SIGNAL(triggered()), q_ptr, slot);
    return act;
}

bool DirTreePrivate::brandExists(const QString& brand)
{
    return childExists(this->rootItem, brand);
}

bool DirTreePrivate::modelExists(const QString& model)
{
    return childExists(q_ptr->currentItem(), model);
}

bool DirTreePrivate::childExists(QTreeWidgetItem* parent, const QString& text)
{
    bool exists = false;
    for (int i = 0; i < parent->childCount(); ++i) {
        if (parent->child(i)->text(DataColumn).compare(text, Qt::CaseInsensitive) == 0) {
            exists = true;
            break;
        }
    }
    return exists;
}

static inline bool isEmptyDir(const QString& dir)
{
    QDir folder(dir);
    return folder.entryList(QDir::Files | QDir::NoDotAndDotDot).isEmpty();
}

DirTree::DirTree(QWidget *parent) : QTreeWidget(parent)
{
    d_ptr = new DirTreePrivate(this);
    setRoot("F:\\MOTORIMAGES");
    header()->hide();
    setAlternatingRowColors(true);

    connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), SLOT(onItemDblClicked(QTreeWidgetItem*,int)));
    connect(this, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(onItemClicked(QTreeWidgetItem*,int)));
}

DirTree::~DirTree()
{
    delete d_ptr;
}

QString DirTree::currentDir() const
{
    QTreeWidgetItem* citem = currentItem();
    if (!citem)
        return QString();

    return citem->data(DataColumn, DataRole).toString();
}

bool DirTree::reload()
{
    // only load directories
    if (d_ptr->root.isEmpty() || !QFileInfo::exists(d_ptr->root))
        return false;

    QDir rootDir(d_ptr->root);
    QFileInfoList subDirs = rootDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
    if (subDirs.isEmpty())
        return false;

    QFileInfo rootFi(d_ptr->root);

    if (!d_ptr->rootItem) {
        d_ptr->rootItem = new QTreeWidgetItem();
        d_ptr->rootItem->setData(DataColumn, ItemTypeRole, Root);
        d_ptr->rootItem->setText(DataColumn, rootFi.baseName());
        d_ptr->rootItem->setData(DataColumn, DataRole, QDir::toNativeSeparators(d_ptr->root));

        QString rootTip;
        rootTip += QString("%1\n").arg(d_ptr->root);
        rootTip += tr("Brand count:%1").arg(Jcr::Utils::dirCount(d_ptr->root));
        d_ptr->rootItem->setToolTip(DataColumn, rootTip);
        addTopLevelItem(d_ptr->rootItem);
    }

    traverseDir(d_ptr->rootItem, d_ptr->root, Brand);
    expandAll();
}

bool DirTree::find(const QString& keyword)
{
    QList<QTreeWidgetItem*> items = this->findItems(keyword, Qt::MatchContains | Qt::MatchRecursive);
    if (items.isEmpty())
        return false;

    QTreeWidgetItem* firstItem = items.first();
    this->setCurrentItem(firstItem);
    return true;
}

void DirTree::setRoot(const QString& root)
{
    if (d_ptr->root != root) {
        d_ptr->root = root;
        reload();
    }
}

QString DirTree::root() const
{
    return d_ptr->root;
}

void DirTree::renameCurrentDir()
{
    DEBUG_WHERE;
}

void DirTree::deleteCurrentDir()
{
    DEBUG_WHERE;
}

void DirTree::locateCurrentDir()
{
    QTreeWidgetItem* currItem = this->currentItem();
    if (currItem) {
        QString path = currItem->data(DataColumn, DataRole).toString();
        Jcr::Utils::locate(path);
    }
}

void DirTree::createDir(const QString& name)
{
    DEBUG_WHERE;
}

void DirTree::contextMenuEvent(QContextMenuEvent *e)
{
    QTreeWidgetItem* currItem = currentItem();
    if (!currItem)
        return;

    ItemType it = static_cast<ItemType>(currItem->data(DataColumn, ItemTypeRole).toInt());
    if (it == Root) {
        d_ptr->rootMenu->exec(e->globalPos());
        e->accept();
    } else if (it == Brand) {
        d_ptr->brandMenu->exec(e->globalPos());
        e->accept();
    } else if (it == Model){
        d_ptr->modelMenu->exec(e->globalPos());
        e->accept();
    } else {
        e->ignore();
    }
}

void DirTree::keyPressEvent(QKeyEvent *e)
{
    bool isCtrlPressed = e->modifiers() & Qt::ControlModifier;
    bool isShiftPressed = e->modifiers() & Qt::ShiftModifier;

    switch (e->key()) {
    case Qt::Key_C:
        if (isCtrlPressed)
            copyTextToClipboard();
        else if (isShiftPressed)
            copyDataToClipboard();
        break;

    case Qt::Key_F5:
        reload();
        break;
    }
}

void DirTree::onItemDblClicked(QTreeWidgetItem* item, int column)
{
    ItemType it = static_cast<ItemType>(item->data(column, ItemTypeRole).toInt());
    if (it == Model) {
        d_ptr->currentDir = item->data(DataColumn, DataRole).toString();
        emit load(d_ptr->currentDir);
    }
}

void DirTree::onItemClicked(QTreeWidgetItem* item, int column)
{
    Q_UNUSED(column)
    emit notify(item->data(DataColumn, DataRole).toString());
    emit modelChanged(item->text(DataColumn));
}

void DirTree::locate()
{
    QTreeWidgetItem* currItem = this->currentItem();
    if (!currItem)
        return;

    QString path = currItem->data(DataColumn, DataRole).toString();
    Jcr::Utils::locate(path);
}

void DirTree::addBrand()
{
    QString brand = QInputDialog::getText(this, tr("New Brand"), tr("Brand Name:"));
    if (brand.isEmpty())
        return;

    if (d_ptr->brandExists(brand)) {
        QMessageBox::warning(this, tr("Warning"), tr("Brand %1 has already exists!").arg(brand));
        return;
    }

    const QString root = this->root();
    const QString brandPath = QString("%1/%2").arg(root).arg(brand);
    QDir dir(brandPath);
    dir.mkdir(brandPath);

    QTreeWidgetItem* brandItem = new QTreeWidgetItem;
    brandItem->setText(DataColumn, brand);
    brandItem->setData(DataColumn, DataRole, QDir::toNativeSeparators(brandPath));
    brandItem->setData(DataColumn, ItemTypeRole, Brand);
    d_ptr->rootItem->addChild(brandItem);
}

void DirTree::renameBrand()
{
    DEBUG_WHERE;
}

void DirTree::deleteBrand()
{
    QTreeWidgetItem* citem = currentItem();
    if (!citem)
        return;

    const QString path = citem->data(DataColumn, DataRole).toString();
    int q = QMessageBox::question(this, tr("Attention"), tr("Confirm to remove directory: %1 recursively ?").arg(path), QMessageBox::Yes | QMessageBox::No);

    if (q == QMessageBox::Yes) {
#if 0
        QDir brandDir(path);
        bool ok = brandDir.removeRecursively();
#endif
        delete citem;
    }
}

void DirTree::addModel()
{
    QTreeWidgetItem* citem = currentItem();
    if (!citem)
        return;

    QString model = QInputDialog::getText(this, tr("New Model"), tr("Model Name:"));
    if (model.isEmpty())
        return;

    if (d_ptr->modelExists(model)) {
        QMessageBox::warning(this, tr("Warning"), tr("Model %1 has already exists!").arg(model));
        return;
    }

    const QString brandPath = citem->data(DataColumn, DataRole).toString();
    const QString modelPath = QString("%1/%2").arg(brandPath).arg(model);
    QDir dir(modelPath);
    dir.mkdir(modelPath);
    const QString localPath = QDir::toNativeSeparators(modelPath);

    QTreeWidgetItem* modelItem = new QTreeWidgetItem;
    modelItem->setText(DataColumn, model);
    modelItem->setData(DataColumn, DataRole, localPath);
    modelItem->setToolTip(DataColumn, localPath);
    modelItem->setData(DataColumn, ItemTypeRole, Brand);
    citem->addChild(modelItem);
    setCurrentItem(modelItem);
}

void DirTree::renameModel()
{
    DEBUG_WHERE;
}

void DirTree::deleteModel()
{
    QTreeWidgetItem* citem = currentItem();
    if (!citem)
        return;

    const QString path = citem->data(DataColumn, DataRole).toString();
    int q = QMessageBox::question(this, tr("Attention"), tr("Confirm to delete model directory: %1").arg(path), QMessageBox::Yes | QMessageBox::No);
    if (q == QMessageBox::Yes) {
        QDir d(path);
        d.removeRecursively();
        delete citem;
    }
}

void DirTree::monitorClipboardDataChanged()
{
    QClipboard* cb = qobject_cast<QClipboard*>(sender());
    const QMimeData* md = cb->mimeData();
    if (!md)
        return;

    const QString strUrl = cb->mimeData()->data("text/plain");
    QFileInfo fi(strUrl);
    const QString ext = fi.suffix().toLower();
    if (ext == "jpg" || ext == "png" || ext == "gif" || ext == "jpeg")
        emit download(QUrl(strUrl));
}

void DirTree::copyTextToClipboard()
{
    QTreeWidgetItem* citem = currentItem();
    if (!citem)
        return;

    QString text = citem->text(DataColumn);
    QApplication::clipboard()->setText(text);
}

void DirTree::copyDataToClipboard()
{
    QTreeWidgetItem* citem = currentItem();
    if (!citem)
        return;

    QString data = citem->data(DataColumn, DataRole).toString();
    QApplication::clipboard()->setText(data);
}

void DirTree::traverseDir(QTreeWidgetItem* parentItem, const QString& dirPath, ItemType it)
{
    QDir dir(dirPath);
    QFileInfoList subDirs = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
    if (subDirs.size() == 0)
        return;

    foreach (auto subDir, subDirs) {
        const QString path = QDir::toNativeSeparators(subDir.absoluteFilePath());
        QTreeWidgetItem* dirItem = new QTreeWidgetItem;

        if (parentItem != d_ptr->rootItem && isEmptyDir(path))
            dirItem->setTextColor(0, Qt::red);

        QString strTip;
        strTip += path + "\n";
        if (it == Brand)
            strTip += tr("Model count: %1").arg(Jcr::Utils::dirCount(path));
        else if (it == Model)
            strTip += tr("Image count: %1").arg(Jcr::Utils::fileCount(path));

        dirItem->setText(DataColumn, subDir.baseName());
        dirItem->setData(DataColumn, DataRole, path);
        dirItem->setToolTip(DataColumn, strTip);
        dirItem->setData(DataColumn, ItemTypeRole, it);
        parentItem->addChild(dirItem);
        traverseDir(dirItem, path, Model);
    }
}

} // namespace jcr
